package co.touchlab.skie.buildsetup.util.version

import org.gradle.api.Project

object SupportedKotlinVersionProvider {

    fun getPrimaryKotlinVersion(project: Project): SupportedKotlinVersion {
        val enabledKotlinVersions = getEnabledKotlinVersions(project)

        val activeSupportedVersion = enabledKotlinVersions.maxByOrNull { it.name }
            ?: error(
                "Primary Kotlin version cannot be determined as no supported versions are enabled. " +
                    "Check for mistakes in the `versionSupport.kotlin.enabledVersions` property.",
            )

        return activeSupportedVersion
    }

    fun getKlibCompilerVersion(project: Project): KotlinToolingVersion =
        project.findProperty("versionSupport.kotlin.klibCompiler")
            ?.toString()
            ?.let(::KotlinToolingVersion)
            ?: getMinimumSupportedKotlinVersion(project)

    fun getEnabledKotlinVersions(project: Project): List<SupportedKotlinVersion> =
        getSupportedKotlinVersions(project).filter { it.isEnabled }

    fun getMinimumSupportedKotlinVersion(project: Project): KotlinToolingVersion =
        getSupportedKotlinVersions(project).flatMap { it.supportedVersions }.min()

    /**
     * Supported format:
     * ```
     * versionSupport.kotlin=1.8.0, 1.8.20(1.8.21, 1.8.22), 1.9.0[1.9.0-RC](1.9.10), 1.9.20[1.9.20-RC]
     * ```
     *
     * The first version is used as the name of the component and as the compiler version.
     * The [] parentheses can be used to override the compiler version, which can be useful for new version development
     * against and RC version when the full version is not available yet.
     * Otherwise, we would have to rename everything once the full version is released.
     *
     * The () parentheses can be used to specify other supported versions (the compiler version is supported by default).
     *
     * Note: The compiler version can be alternatively set by setting `versionSupport.kotlin.enabledVersions` to one of the other supported versions.
     */
    fun getSupportedKotlinVersions(project: Project): List<SupportedKotlinVersion> {
        val supportedKotlinVersions = project.property("versionSupport.kotlin").toString()

        val versions = splitVersions(supportedKotlinVersions)

        val enabledVersions = findParsedEnabledVersions(project)

        return versions.map { parseVersion(it, enabledVersions) }
    }

    private fun splitVersions(supportedKotlinVersions: String): List<String> {
        val result = mutableListOf<String>()

        var isInParentheses = false
        var from = 0
        var to = 0

        fun cutNext() {
            val identifier = supportedKotlinVersions.substring(from, to).trim()
            result.add(identifier)
            from = to + 1
        }

        while (to <= supportedKotlinVersions.lastIndex) {
            when (supportedKotlinVersions[to]) {
                ',' -> {
                    if (!isInParentheses) {
                        cutNext()
                    }
                }
                '(' -> isInParentheses = true
                ')' -> isInParentheses = false
            }

            to++
        }

        cutNext()

        return result
    }

    private val kotlinVersionRegex = "([^\\[(\\])]+)(?:\\[([^\\[(\\])]+)])?(?:\\(([^\\[(\\])]+)\\))?".toRegex()

    private fun parseVersion(version: String, enabledVersions: List<EnabledVersion>?): SupportedKotlinVersion {
        val trimmedVersion = version.trim()

        val match = kotlinVersionRegex.matchEntire(trimmedVersion) ?: error("Invalid Kotlin version identifier: $trimmedVersion")

        val nameString = match.groups[1]?.value
            ?: error("Invalid Kotlin version identifier - missing name: $trimmedVersion")

        val name = KotlinToolingVersion(nameString)

        val otherSupportedVersionNames = match.groups[3]?.value
            ?.split(",")
            ?.map { it.trim() }
            ?: emptyList()

        val otherSupportedVersions = otherSupportedVersionNames.map(::KotlinToolingVersion)

        val correspondingEnabledVersion = enabledVersions?.find { it.nameString == nameString }

        val compilerVersion = correspondingEnabledVersion?.compilerVersion ?: match.groups[2]?.value?.let(::KotlinToolingVersion) ?: name

        return SupportedKotlinVersion(
            name = name,
            compilerVersion = compilerVersion,
            otherSupportedVersions = otherSupportedVersions,
            isEnabled = enabledVersions == null || correspondingEnabledVersion != null,
        )
    }

    private fun findParsedEnabledVersions(project: Project): List<EnabledVersion>? =
        project.findProperty("versionSupport.kotlin.enabledVersions")
            ?.toString()
            ?.split(",")
            ?.map { it.trim() }
            ?.filter { it.isNotBlank() }
            ?.map { version ->
                val name = version.substringBefore("[").trim()
                val compilerVersion = version.takeIf { '[' in it }?.substringAfter("[")?.substringBefore("]")?.trim()

                EnabledVersion(
                    nameString = name,
                    compilerVersion = compilerVersion?.let { KotlinToolingVersion(it) },
                )
            }
            ?.takeIf { it.isNotEmpty() }

    data class EnabledVersion(val nameString: String, val compilerVersion: KotlinToolingVersion?)
}
